import schedule  # 需要安装:pip install schedule -i https://mirrors.aliyun.com/pypi/simple (订阅数据用到)
import stomp  # 需要安装: pip install stomp-py -i https://mirrors.aliyun.com/pypi/simple (订阅数据用到)
import json
import hmac
import base64
import hashlib
import ssl
import threading
import time
from asgiref.sync import async_to_sync
from app01 import models


# ###############################  执行阿里云物联网平台的订阅服务(还有其他服务,这个简单一点) #############################
# 要点: 1.进入设备之前,需要验证你进入的是哪个平台,用哪个身份登录的,即登录5元组(就是下边要用到的,从平台上可以取到)
#      2.登录完成后,需要进入哪个实例的哪个产品,的哪个设备,即设备3元组(就是下边要用到的,从平台上可以取到)
def stomp_amqp(dict_iot, channel_layer_manager):
    # 工程代码泄露可能会导致 AccessKey 泄露，并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用，仅供参考
    # accessKey = os.environ['ALIBABA_CLOUD_ACCESS_KEY_ID']  # 这个是阿里提供公用的,应该也可以进,没试过
    # accessSecret = os.environ['ALIBABA_CLOUD_ACCESS_KEY_SECRET'] # 这个是阿里提供公用的,应该也可以进,没试过
    accessKey = dict_iot.get('accessKey')  # 5元组之1 (举例: accessKey = "LTAI5tKw5CZYsy5SQGGhw7Ac")
    accessSecret = dict_iot.get('accessSecret')  # 5元组之2 (举例 accessSecret = "vJawfCAoz3m6lozIxsyOLyewxmXdcB")
    consumerGroupId = dict_iot.get('consumerGroupId')  # 5元组之3(消费组: 举例: consumerGroupId = "DEFAULT_GROUP")
    iotInstanceId = dict_iot.get('iotInstanceId')  # 5元组之4 (实例ID, 举例: iotInstanceId = "iot-06z00anzg7xtx43")
    clientId = dict_iot.get('clientId')  # # 5元组之5 (客户端ID,举例: clientId = "zwd1969")

    device_name = dict_iot.get('device_name')  # 设备名(用户登录的设备名)

    def connect_and_subscribe(conn):
        # 签名方法：支持hmacmd5，hmacsha1和hmacsha256。(阿里自定义的,照抄就好)
        signMethod = "hmacsha1"
        timestamp = current_time_millis()
        # userName组装方法，请参见AMQP客户端接入说明文档。
        # 若使用二进制传输，则userName需要添加encode=base64参数，服务端会将消息体base64编码后再推送。
        username = clientId + "|authMode=aksign" + ",signMethod=" + signMethod \
                   + ",timestamp=" + timestamp + ",authId=" + accessKey \
                   + ",iotInstanceId=" + iotInstanceId \
                   + ",consumerGroupId=" + consumerGroupId + "|"
        signContent = "authId=" + accessKey + "&timestamp=" + timestamp
        # 计算签名，password组装方法，请参见AMQP客户端接入说明文档。
        password = do_sign(accessSecret.encode("utf-8"), signContent.encode("utf-8"))

        conn.set_listener('', MyListener(conn))
        conn.connect(username, password, wait=True)
        # 清除历史连接检查任务，新建连接检查任务
        schedule.clear('conn-check')
        schedule.every(1).seconds.do(do_check, conn).tag('conn-check')

    class MyListener(stomp.ConnectionListener):
        def __init__(self, conn):
            self.conn = conn
            # self.channel_layer_manager = ChannelLayerManager()  # 共享队列，自己建立的websocket用的

        def on_error(self, frame):
            print('收到错误 "%s"' % frame.body)

        def on_message(self, frame):
            # print('received a message "%s"' % frame.body)  # 官方提供的打印语句,不需要
            data = json.loads(frame.body)
            # 因为阿里不管信息有用否,有心跳就要促动这里造成错误,所以要判断传来的是否有要用的items数据,再try一下更保证
            # 即:有设备数据更新阿里会给你传来数据,没有则会传来连接状态信息
            if data.get('items') is not None:
                try:
                    deviceName = data.get('deviceName')  # 阿里来的设备名
                    items = data.get('items')  # 阿里来的数据
                except Exception as e:
                    print(e)
                    return
                # 把接收到的数据存入数据库
                data1 = data.get("deviceType")
                data2 = data.get("iotId")
                data3 = data.get("requestId")
                data4 = json.dumps(data.get("checkFailedData"))  # 字典需要序列化成json字符串
                data5 = data.get("productKey")
                data6 = data.get("gmtCreate")
                data7 = data.get("deviceName")
                data8 = json.dumps(data.get("items"))  # 字典需要序列化成json字符串
                toDevice_id = models.Device.objects.get(device_name=data7).id
                DeviceData = models.DeviceData(deviceType=data1,
                                               iotId=data2,
                                               requestId=data3,
                                               checkFailedData=data4,
                                               productKey=data5,
                                               gmtCreate=data6,
                                               deviceName=data7,
                                               items=data8,
                                               toDevice_id=toDevice_id,
                                               )
                DeviceData.save()
                # 发送到共享队列，给前端发送过去,更新页面
                time.sleep(0.3)
                async_to_sync(channel_layer_manager.channel_layer.group_send)(
                    data7,  # group组
                    {
                        "type": "send.data",  # 去调用send_data()函数
                        "message": data8,  # 在send_data()里发送data8数据
                    }
                )

        def on_heartbeat_timeout(self):
            print('on_心跳_超时')  # print('on_heartbeat_timeout')

        def on_connected(self, headers):
            # print("已成功连接")  # print("successfully connected")
            conn.subscribe(destination='/topic/#', id='1', ack='auto')
            print("成功订阅")  # print("successfully subscribe")

        def on_disconnected(self):
            print('已断开')  # print('disconnected')
            connect_and_subscribe(self.conn)

    def current_time_millis():
        return str(int(round(time.time() * 1000)))

    def do_sign(secret, sign_content):
        m = hmac.new(secret, sign_content, digestmod=hashlib.sha1)
        return base64.b64encode(m.digest()).decode("utf-8")

    # 检查连接，如果未连接则重新建连
    def do_check(conn):
        # print('检查是否连接: %s' % conn.is_connected())
        if not conn.is_connected():
            try:
                connect_and_subscribe(conn)
            except Exception as e:
                print('已断开,错误信息: ', e)  # print('disconnected, ', e)

    # 定时任务方法，检查连接状态
    # def connection_check_timer():
    #     while 1:
    #         schedule.run_pending()
    #         time.sleep(10)
    def connection_check_timer(thread_instance=None):  # 添加可选参数
        while True:
            if not conn.is_connected():
                try:
                    conn.connect(heartbeats=(300000, 300000), wait=True)  # 带重连
                except stomp.exception.ConnectFailedException as e:
                    print(f"重连失败: {e}")
            time.sleep(30)  # 每30秒检查一次

    #  接入域名，请参见AMQP客户端接入说明文档。这里直接填入域名，不需要带amqps://前缀
    # conn = stomp.Connection([('%s.amqp.iothub.aliyuncs.com' % iotInstanceId, 61614)], heartbeats=(0, 300))
    # conn.set_ssl(for_hosts=[('%s.amqp.iothub.aliyuncs.com' % iotInstanceId, 61614)],
    #              ssl_version=ssl.PROTOCOL_TLS)

    # 符合阿里云规范的连接配置
    conn = stomp.Connection(
        [('%s.amqp.iothub.aliyuncs.com' % iotInstanceId, 61614)],
        # heartbeats=(300000, 300000),  # 300秒双向心跳 ← 核心修复
        heartbeats=(0, 600),
        reconnect_attempts_max=3,  # 自动重连3次
        heart_beat_receive_scale=1.5  # 接收超时 = 1.5 × 声明值
    )
    conn.set_ssl(ssl_version=ssl.PROTOCOL_TLSv1_2)

    try:
        connect_and_subscribe(conn)
    except Exception as e:
        print('连接失败')
        raise e

    # 异步线程运行定时任务，检查连接状态
    # thread = threading.Thread(target=connection_check_timer)
    # thread.start()
    # 启动线程
    threading.Thread(target=connection_check_timer, args=(conn,), daemon=True).start()
